[Remove Vuetify from Studio] 'Create an account' page#5701
[Remove Vuetify from Studio] 'Create an account' page#5701vtushar06 wants to merge 9 commits intolearningequality:unstablefrom
Conversation
…ponents and added emailValidateMessage translation
…mplement toggleUsage method for form handling
…xtbox components, enhancing form handling and user experience
…ndling for agreement acceptance
|
👋 Thanks for contributing! We will assign a reviewer within the next two weeks. In the meantime, please ensure that:
We'll be in touch! 😊 |
There was a problem hiding this comment.
Pull request overview
This pull request migrates the "Create an account" page from Vuetify to the Kolibri Design System (KDS) as part of the ongoing effort to remove Vuetify dependencies from Studio (issue #5060). The migration replaces Vuetify form components with KDS equivalents and implements custom form validation logic to replace Vuetify's built-in validation system.
Changes:
- Replaced Vuetify components (
VForm,VSelect,VSlideYTransition,VLayout,VInput) with KDS components (KSelect,KTransition,KCheckbox,KTextbox,KButton,KRouterLink) - Created two new wrapper components
StudioEmailFieldandStudioPasswordFieldfor consistent email and password input handling - Implemented custom form validation with
validateField()andvalidateForm()methods to replace Vuetify's validation system
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| contentcuration/contentcuration/frontend/accounts/pages/Create.vue | Main migration: replaced Vuetify components with KDS, implemented custom validation, updated styling |
| contentcuration/contentcuration/frontend/accounts/components/form/StudioEmailField.vue | New wrapper component for email input using KTextbox with validation and error display |
| contentcuration/contentcuration/frontend/accounts/components/form/StudioPasswordField.vue | New wrapper component for password input using KTextbox with error display |
Comments suppressed due to low confidence (1)
contentcuration/contentcuration/frontend/accounts/components/form/StudioPasswordField.vue:50
- According to the acceptance criteria in issue #5670, if there is no unit test suite, a new one should be created using @testing-library/vue. No unit tests have been added for the Create page or the new StudioEmailField and StudioPasswordField components.
Consider adding unit tests for the new components and the updated Create page to verify the form validation logic, error handling, and user interactions work as expected.
<template>
<KTextbox
:value="value"
type="password"
:label="label || $tr('passwordLabel')"
:invalid="hasError"
:invalidText="errorText"
:showInvalidText="hasError"
@input="$emit('input', $event)"
@blur="$emit('blur')"
/>
</template>
<script>
export default {
name: 'StudioPasswordField',
props: {
value: {
type: String,
default: '',
},
label: {
type: String,
default: null,
},
errorMessages: {
type: Array,
default: () => [],
},
},
computed: {
hasError() {
return this.errorMessages && this.errorMessages.length > 0;
},
errorText() {
return this.hasError ? this.errorMessages[0] : '';
},
},
$trs: {
passwordLabel: 'Password',
},
};
</script>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
contentcuration/contentcuration/frontend/accounts/components/form/StudioEmailField.vue
Show resolved
Hide resolved
|
Hi @vtushar06, thanks! Before we assign a maintainer, I will invite the community review. |
|
📢 ✨ Community Review guidance for both authors and reviewers. |
|
Yeah sure @MisRob, I would love to get engage with our fellow contributors over this. |
|
Hey @vtushar06 @MisRob , are there existing tests for the Create page that would cover the new StudioEmailField, StudioPasswordField, and validation logic? Or would new tests be needed as mentioned in the issue's acceptance criteria? |
|
Hi @abhiraj75, thanks! The page itself was covered with VTL tests #5633 beforehand which I mentioned to @vtushar06. However still, having few focused tests for the new |
|
thanks @MisRob, I will proceed my upcoming changes including tests for |
LianaHarris360
left a comment
There was a problem hiding this comment.
Thanks for your work on this @vtushar06! The newly created components are clean, focused, and correctly match the use case of the original components used. I had a few questions & notes on the changes within Create.vue that need to be resolved.
| break; | ||
| } | ||
| }, | ||
| validateForm() { |
There was a problem hiding this comment.
I noticed this manual validateField() / validateForm() system is used to replace the Vuetify form validation. Was there any particular reason to not use generateFormMixin for the validation instead?
| // We need to check the "acceptedAgreement" here explicitly because it is not a | ||
| // Vuetify form field and does not trigger the form validation. | ||
| if (this.$refs.form.validate() && this.acceptedAgreement) { | ||
| if (this.validateForm() && this.acceptedAgreement) { |
There was a problem hiding this comment.
Does the previous comment about the acceptedAgreement field not being a Vuetify form field still apply? Is validation triggered if we don't explicitly check "acceptedAgreement" here?
| :style="{ color: $themeTokens.error }" | ||
| class="field-error" | ||
| > | ||
| {{ $tr('fieldRequired') }} |
There was a problem hiding this comment.
Was there an issue with using commonStrings.$tr('fieldRequired') in the template? This file uses two sources for this string, but it would be best to stick to one.
| other_use: '', | ||
| locations: [], | ||
| source: '', | ||
| source: {}, |
There was a problem hiding this comment.
There's an error in the console: [Vue warn]: Invalid prop: custom validator check failed for prop "value" because of how the source field is initialized. Can you update this to be source: { value: '', label: '' }, instead?
|
|
||
| .banner { | ||
| margin-bottom: 32px; | ||
| } |
| > | ||
| <Banner | ||
| :value="!valid" | ||
| <StudioBanner |
|
@vtushar06 @LianaHarris360 we recently did these changes to alert banners that for some reason don't take place here. @vtushar06 perhaps you started the branch before the banner update was merged? Can you merge the latest |
| const input = screen.getByLabelText(/^password$/i); | ||
| await fireEvent.update(input, ' mypassword '); | ||
| expect(emitted().input).toBeTruthy(); | ||
| expect(emitted().input[0][0]).toBe(' mypassword '); |
There was a problem hiding this comment.
@LianaHarris360 unless you already know, would you please be able to double-check on whether not trimming password is indeed expected and how it plays with backend? I would just like to be sure we won't cause any issues with passwords.
There was a problem hiding this comment.
(also, the intention is to eventually use StudioPasswordField not only on Create account page, but we will also use it on Login page and Reset password page ~ even though not in scope of this particular PR)
|
@vtushar06 we'll also turn on our |
| <script> | ||
|
|
||
| import { mapActions, mapGetters, mapState } from 'vuex'; | ||
| import KTextbox from 'kolibri-design-system/lib/KTextbox'; |
There was a problem hiding this comment.
No need to import KDS components. They are registered globally.
There was a problem hiding this comment.
Solid migration from Vuetify to KDS — the form works correctly, validation is functional, and both LTR/RTL recordings are appreciated.
CI passing. Video recordings verified — layout and interactions look clean.
- suggestion: Spec calls for
generateFormMixinbut custom validation used (see inline) - suggestion: Inconsistent "field required" strings (see inline)
- suggestion: Existing
create.spec.jsskipped tests could be partially re-enabled now (see inline) - praise: Good
@testing-library/vuetest coverage for new wrapper components - praise: Both RTL and LTR recordings provided
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Reviewed the pull request diff checking for:
- Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
- Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
- Architecture: duplicated concerns, minimal interfaces, composition over inheritance
- Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
- Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
- Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
- Checked CI status and linked issue acceptance criteria
- For UI changes: inspected screenshots for layout, visual completeness, and consistency
| showOtherField(id) { | ||
| return id === uses.OTHER && this.form.uses.includes(id); | ||
| }, | ||
| validateField(field) { |
There was a problem hiding this comment.
suggestion: Issue #5670 specifies "Replace VForm with <form> and use generateFormMixin for validation," but this PR implements a custom validateField/validateForm approach instead. The custom approach works, but the generateFormMixin (used by ChangePasswordForm, FullNameForm, RequestForm, etc.) would reduce boilerplate and align with the project's established validation pattern.
Was deviating from the spec intentional? If there's a reason generateFormMixin doesn't fit this form (e.g., the complex multi-section structure), it might be worth noting in the PR description.
| otherSourcePlaceholder: 'Please describe', | ||
|
|
||
| // Privacy policy + terms of service | ||
| fieldRequired: 'Field is required', |
There was a problem hiding this comment.
suggestion: This introduces a new translation string 'Field is required', but commonStrings.$tr('fieldRequired') already provides 'This field is required' (from shared/translator.js). The validateField method uses the shared string while the template (lines 133, 147, 185) uses this local one, resulting in two different messages for the same concept.
Consider removing this local fieldRequired entry and using commonStrings.$tr('fieldRequired') consistently — or, if the different wording is intentional, align them.
| const input = screen.getByLabelText(/email address/i); | ||
| await fireEvent.update(input, ' test@example.com '); | ||
| expect(emitted().input).toBeTruthy(); | ||
| expect(emitted().input[0][0]).toBe('test@example.com'); |
There was a problem hiding this comment.
praise: Good test coverage — rendering, input handling (trimming), blur events, and error display are all covered using @testing-library/vue as the acceptance criteria require. The parallel StudioPasswordField tests similarly cover the raw (untrimmed) password input behavior, which is an important distinction.
| :rules="sourceRules" | ||
| class="mt-2" | ||
| <h2 class="section-header">{{ $tr('sourceLabel') }}*</h2> | ||
| <KSelect |
There was a problem hiding this comment.
praise: Clean use of KSelect with v-model binding and placeholder — simpler than the previous sourceValue computed + @change handler pattern.
rtibblesbot
left a comment
There was a problem hiding this comment.
Clean Vuetify-to-KDS migration. CI passing. Video recordings verified from prior review.
0 prior finding(s) resolved.
Prior finding status:
- UNADDRESSED: RTL
margin-leftshould use logical properties (see inline) - UNADDRESSED: Inconsistent "field required" strings between template and
validateField(see inline) - UNADDRESSED: Skipped tests in
create.spec.js(lines 120, 147) cite Vuetify as the blocker — now that KDS components replace Vuetify on this page, those tests should be un-skippable. Consider re-enabling them in this PR. - ACKNOWLEDGED: Custom
validateField/validateForminstead ofgenerateFormMixin— understood as a design choice for this complex multi-section form.
Findings:
- suggestion: RTL logical properties (see inline)
- suggestion: Inconsistent required-field messages (see inline)
- suggestion: Re-enable skipped
create.spec.jstests (noted above) - praise: Defensive source value extraction in
clean()(see inline)
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
| .conditional-field { | ||
| margin-top: 8px; | ||
| margin-bottom: 8px; | ||
| margin-left: 32px; |
There was a problem hiding this comment.
suggestion (UNADDRESSED from prior review): margin-left doesn't flip in RTL layouts. Use margin-inline-start instead.
This applies to all new margin-left declarations in this file:
.conditional-field(line 734).conditional-field-textarea(line 740).policy-error(line 758).span-spacing(line 764).span-spacing span(line 767).contact-message(line 774)
There was a problem hiding this comment.
Let's ignore this comment. We use RTLCSS https://kolibri-dev.readthedocs.io/en/develop/i18n.html#text-alignment that will automatically change margin-left to margin-right.
| // Privacy policy + terms of service | ||
| fieldRequired: 'Field is required', | ||
| viewToSLink: 'View Terms of Service', | ||
| ToSRequiredMessage: 'Please accept our terms of service and policy', |
There was a problem hiding this comment.
suggestion (UNADDRESSED from prior review): This local string 'Field is required' is used by the template (lines 133, 147, 185) for usage/location/source validation, while commonStrings.$tr('fieldRequired') — which produces 'This field is required' — is used by validateField() for text input validation (lines 488–537). Users see two different messages for the same concept.
Consider removing this local entry and using commonStrings.$tr('fieldRequired') consistently, or updating the template to reference the shared string.
| // Trim text fields | ||
| if (key === 'source') { | ||
| if (cleanedData[key] === sources.ORGANIZATION) { | ||
| const sourceValue = |
There was a problem hiding this comment.
praise: Good defensive handling of the source value — gracefully accommodates both the KSelect object format ({value, label}) and plain strings, preventing runtime errors during the migration.
|
Sorry I think the bot got wild here because at first I removed one if its comments around RTL :) Still experimenting. |



Summary
Migrates the Create an account page from Vuetify to Kolibri Design System (KDS) as part of issue #5670.
Changes:
VForm,VSelect,VSlideYTransition,VLayout,VInputwith KDS equivalents (KSelect,KTransition,KCheckbox,KTextbox,KButton,KRouterLink)TextField,EmailField,PasswordField,TextArea,Checkbox,Banner,ImmersiveModalLayout) with KDS components and two new wrapper components:StudioEmailField.vue — wraps KTextbox with email input handling and error display
StudioPasswordField.vue — wraps KTextbox with password type and error display
$refs.form.validate()+ computed rule arrays) with a customvalidateField()/validateForm()approach that:ActionLinkpolicy links withKRouterLinkScreenRecording:
Remove-vuetify-5670-2.mp4
Remove-vuetify-5670.mp4
…
References
Closes #5670
…
Reviewer guidance
Login as a@a.com with password a
Go to Create Page